SPB Git forge
38commits 1branches 0releases
338.7 MBsize
maindefault branch
7 h agolast push
HTML 53.9% TypeScript 44.5% JavaScript 0.6% SQL 0.5%
2.7 KB · 57 lines typescript
Raw Blame History
1import { api } from "@/lib/api";2import { SITE_URL } from "@/lib/routes";3import { KIND_FREQ, KIND_HREF, STATIC_PAGES, parseSitemapId } from "../../sitemap.xml/ids";45/**6 * Per-id sitemaps at /sitemap/<id>.xml — `static` for the fixed pages, `<kind>-<page>` for each entity kind7 * (5 000 URLs per API page, see /api/v1/sitemap/:kind). Listed by the index at /sitemap.xml.8 * Hand-rolled instead of Next's `generateSitemaps` because that convention also claims /sitemap.xml itself9 * (and crashes there without an id), which robots.txt points to.10 */11export const revalidate = 3600;1213const esc = (s: string) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");1415interface Entry {16  url: string;17  lastModified?: Date;18  changeFrequency?: string;19  priority?: number;20}2122function toXml(entries: Entry[]): string {23  const body = entries24    .map((e) => {25      const parts = [`<loc>${esc(e.url)}</loc>`];26      if (e.lastModified) parts.push(`<lastmod>${e.lastModified.toISOString()}</lastmod>`);27      if (e.changeFrequency) parts.push(`<changefreq>${e.changeFrequency}</changefreq>`);28      if (e.priority !== undefined) parts.push(`<priority>${e.priority}</priority>`);29      return `  <url>${parts.join("")}</url>`;30    })31    .join("\n");32  return `<?xml version="1.0" encoding="UTF-8"?>\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n${body}\n</urlset>\n`;33}3435export async function GET(_req: Request, ctx: { params: Promise<{ id: string }> }): Promise<Response> {36  const { id } = await ctx.params;37  if (!id.endsWith(".xml")) return new Response("Not Found", { status: 404 });38  const parsed = parseSitemapId(id.slice(0, -4));39  if (!parsed) return new Response("Not Found", { status: 404 });4041  let entries: Entry[];42  if (parsed === "static") {43    const now = new Date();44    entries = STATIC_PAGES.map((p) => ({ url: `${SITE_URL}${p.path}`, lastModified: now, changeFrequency: p.changeFrequency, priority: p.priority }));45  } else {46    const r = await api.sitemap(parsed.kind, parsed.page);47    if (!r.ok && r.status !== 404) return new Response("Service Unavailable", { status: 503, headers: { "Retry-After": "300" } });48    const href = KIND_HREF[parsed.kind];49    const priority = parsed.kind === "facilities" || parsed.kind === "countries" ? 0.7 : 0.6;50    entries = (r.ok && Array.isArray(r.data) ? r.data : []).map((e) => {51      const d = new Date(e.updatedAt);52      return { url: `${SITE_URL}${href(e.slug)}`, lastModified: Number.isNaN(d.getTime()) ? undefined : d, changeFrequency: KIND_FREQ[parsed.kind], priority };53    });54  }55  return new Response(toXml(entries), { headers: { "Content-Type": "application/xml; charset=utf-8", "Cache-Control": "public, max-age=3600, s-maxage=3600, stale-while-revalidate=86400" } });56}57